"""爬虫测试"""

#可format(pid)获取题目
probtemp_url="http://acm.hdu.edu.cn/showproblem.php?pid={}"
#题目列表 可format(pageid)获取一个题目页
problist_url="http://acm.hdu.edu.cn/listproblem.php?vol={}"
#提交数据跟踪 format(runid,题目号,用户号)获取实时状态页 其中runid设置后 第一行即此runid的数据  下面是之前的数据（截尾)
#不需要的可以给空（但不能不给）
state_url="http://acm.hdu.edu.cn/status.php?first={}&pid={}&user={}&lang=0&status=0"
#提交代码 POST提交

#编译错误提示
compile_error_url="http://acm.hdu.edu.cn/viewerror.php?rid={}"

"""
必须在登录情况下访问
参数: 
problemid:text 题号
language:0到6  G++ GCC C++ C Pascal Java C#
usercode:代码文本
"""
submit_url="http://acm.hdu.edu.cn/submit.php?action=submit"
language_dict={
    "G++":0,
    "GCC":1,
    "C++":2,
    "C":3,
    "Pascal":4,
    "Java":5,
    "C#":6
}

#登录链接 POST提交
#参数 username userpass login=Sign In
login_url="http://acm.hdu.edu.cn/userloginex.php?action=login"


import requests
from requests import  Session
from bs4 import BeautifulSoup

headers = { "User-Agent" : 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.13 Safari/537.36', "Referer": "http://www.v2ex.com/signin" }

#使用htmlparser的bsoup
BSoup=lambda html:BeautifulSoup(html,"lxml")
#登录
from traits.api import HasTraits
from traits.trait_types import *
from bs4 import Tag

with open("./test.c", "r") as f:
    testcode = f.read()

class Problem(HasTraits):
    pid=Int()
    title=Str("无")
    limit=Str("未知")
    description=Str("无")
    input_desc=Str("无")
    output_desc=Str("无")
    input_sample=Str("无")
    output_sample=Str("无")
    author=Str()

    #提交
    def submit(self,session:Session,username,code,language="G++"):
        """
        进行一次提交
        :param session: 需要使用的session对象
        :param code: 提交的代码
        :param language: 代码语言
        :return: SubmitResult对象 用于跟踪提交状态
        """
        data={
            "problemid":self.pid,
            "usercode":code,
            "language":language_dict[language]
        }
        res=session.post(submit_url,data=data,headers=headers)
        if res.status_code!=200:
            raise Exception("提交错误")
        else:
            #生成SubmitResult 返回
            ret=SubmitResult()
            ret.session=session
            ret.username=username
            ret.problem=self
            #获取runid
            html=res.content
            bsoup=BSoup(html)
            tag=bsoup.find(attrs={"class":"table_text"}) #type:Tag
            #这时候如果进入了提示登录的页面则无法获取接下来的数据
            if tag is None:
                raise Exception("错误,尚未登录无法提交")
            tr=tag.find_all("tr",recursive=True)[1] #type:Tag
            runid=tr.find("td").text
            ret.runid=int(runid)
            ret.code=code
            ret.update_state_from_html(html)
            #测试打印
            # print(ret.state)
            return ret


class SubmitResult(HasTraits):
    problem=Instance(Problem)
    session=Instance(Session)
    username=Str()
    #提交返回页面的第一个item
    runid=Int()
    #提交的代码
    code=Str()
    #内部状态
    state=Str()
    #编译报错
    compile_error=Str("")
    AcceptedState = ["Accepted","User Accepted"]
    CompileErrorState="Compilation Error"
    def need_update(self):
        """判断是否需要更新（是否已经完成)"""
        okstate=[
            "User Accepted",
            "Accepted",
            "Presentation Error",
            "Wrong Answer",
            "Runtime Error",
            "Compilation Error",
            "Time Limit Exceeded",
            "Memory Limit Exceeded",
            "Output Limit Exceeded",
            "System Error"
        ]
        if self.state in okstate:
            return False
        return True

    def _get_compile_text(self):
        if self.state == "Compilation Error":
            # 获取编译错误提示并存储
            url = compile_error_url.format(self.runid)
            res = self.session.get(url, headers=headers)
            if res.status_code >= 200 and res.status_code < 400:
                #解析得到错误提示
                bs=BSoup(res.content)
                errortext=bs.find_all("table")[-2].text
                self.compile_error=errortext
        else:
            self.compile_error=""


    def update_state_from_html(self,html):
        """
        从html中 取第一行作为本result的状态
        :param html:用于获取状态的html
        :return 状态是否发生改变
        """
        bs = BSoup(html)
        tb = bs.find(attrs={"class": "table_text"})  # type:Tag
        # 递归搜索 tr标签 找到的第一个为header 第二个即为需要的runid行
        tags = list(tb.find_all("tr", recursive=True))
        line = tags[1]
        # 第三格即为状态值
        rawstate=self.state
        self.state = line.find_all("td")[2].text
        self._get_compile_text()
        return self.state!=rawstate
    def _update_state(self):
        """
        得到提交的当前状态
        并保存到内部状态state中
        :return:state
        """
        if not self.need_update():
            return self.state
        url=state_url.format(self.runid,self.problem.pid,self.username)
        res=self.session.get(url,headers=headers)
        if res.status_code!=200:
            raise Exception("错误，获取结果页失败")
        html=res.content
        return self.update_state_from_html(html)
    def update_state(self):
        """为防止runid不匹配的错误 这里进行5次runid递增测试"""
        for i in range(5):
            try:
                ischanged=self._update_state()
                return ischanged
            except Exception as e:
                self.runid+=1
                #调试用
                print(f"重试：runid:{self.runid}")
                continue
        #全部失败
        raise Exception(f"状态更新失败，重试次数{i}")






class ProblemDesc(HasTraits):
    pid=Int()
    title=Str()
    ratio=Float()

class WebSeeesion(HasTraits):
    session=Instance(Session) #type:Session
    username=Str()

    def __init__(self):
        super(WebSeeesion,self).__init__()
        self.session=requests.session()
    def login(self,username,password):
        """
        登录 可重复调用 重复登录不出错 将本WebSession的Username填充上
        :param username: 用户名
        :param password: 密码
        :return: 是否成功
        """
        res=self.session.post(login_url,data={"username":username,"userpass":password,"login":"Sign In"},headers=headers)
        #200或 302
        if res.status_code<400:
            #这里根据提交是否报错来检查是否登录成功
            #测试提交
            prob=self.get_problem(1000)
            try:
                prob.submit(self.session,username,testcode)
            except Exception as e:
                #如果提交错误认为登录失败
                return False
            self.username = username
            return True
        return False

    def get_problemList(self,pageid=1):
        """
        获取题目列表
        :param pageid:题目页id
        :return: 一个列表每个item为一个ProblemDesc
        """
        url=problist_url
        res=self.session.get(url.format(pageid))
        html=res.content
        bsoup=BSoup(html)
        #得到表格Tag
        ttag=bsoup.find(attrs={"class":"table_text"}) #type:Tag
        ret=[]
        def p(id,pid,n,title,ac_n,all_n):
            desc=ProblemDesc()
            desc.pid=pid
            desc.title=title
            desc.ratio=ac_n/all_n
            ret.append(desc)
        script=ttag.find("script").text.replace(";","\n") #type:str
        for s in script.split("\n")[:-1]:
            eval(s)
        return ret



    def get_problem(self,pid):
        """
        获取题目内容
        :param pid: 题目id
        :return: 题目内容 字典 {desciption:text,
        """
        url=probtemp_url.format(pid)
        res=self.session.get(url,headers=headers)
        if res.status_code!=200:
            return None
        html=res.content
        bs=BSoup(html)
        conts=list(bs.find_all("div",attrs={"class":"panel_content"}))
        conts=conts+list(bs.find_all("h1"))+[bs.find_all("font")[3]]
        prob=Problem()
        prob.pid=pid
        plist=["description","input_desc","output_desc","input_sample","output_sample","author","title","limit"]
        del conts[6]
        for i in range(min(len(conts),len(plist))):
            setattr(prob,plist[i],conts[i].text)
        return prob

from typing import List


if __name__=="__main__":
    sess=WebSeeesion()
    sess.login("gmono","gmono35")
    pl=sess.get_problemList(1) #type:List[ProblemDesc]



    from time import sleep
    for p in pl:
        p.print_traits()
        pp=sess.get_problem(p.pid)
        pp.print_traits()
        result=pp.submit(sess.session,"gmono",testcode)
        while result.need_update():
            try:
                if(result.update_state()):
                    print(result.state)
            except Exception as e:
                print("提交结果同步错误，重新提交")
                result=pp.submit(sess.session,"gmono",testcode)

            sleep(1)
        sleep(1)
